package ngat.oss.simulation.metrics;

import ngat.oss.simulation.*;
import ngat.oss.simulation.metrics.*;

import ngat.util.*;
import ngat.util.logging.*;
import ngat.astrometry.*;
import ngat.phase2.*;
import ngat.icm.*;

import java.io.*;
import java.rmi.*;
import java.util.*;
import java.awt.geom.*;

/** Calculate the load for a night (or nights).*/
public class LoadCalculator { 

    Site site;
    Phase2Model phase2Model;
    ExecutionHistoryModel historyModel;
    AccountingModel accModel;
    TimingConstraintWindowCalculator tcwc;
    EnvironmentPredictor envModel;    
    ExecutionTimingModel execModel;

    PriorityUtilityCalculator puc;

    LogProxy logger;

    public LoadCalculator(Site                             site,
		   Phase2Model                      phase2Model,
		   ExecutionHistoryModel            historyModel,
		   AccountingModel                  accModel,
		   TimingConstraintWindowCalculator tcwc,
		   EnvironmentPredictor             envModel,    
		   ExecutionTimingModel             execModel) {

	this.site = site;
	this.phase2Model  = phase2Model;
	this.historyModel = historyModel ;
	this.accModel     = accModel;
	this.tcwc         = tcwc;
	this.envModel     = envModel;
	this.execModel    = execModel;
	
	Logger slogger=  LogManager.getLogger("SIM");
	logger = new LogProxy("LCALC","", slogger);

	puc = new PriorityUtilityCalculator(execModel);

    }
    
    /** Calculate the total load between the given times (a night of observing).*/
    public LoadStatistics generateLoadStatistics(long start, long end) throws Exception {

	// Check each valid group in phase2 ODB.
	double sumload  = 0.0; // all
	double critload = 0.0; // critical only
	double pwload   = 0.0; // priority weighted
	double uwload   = 0.0; // urgency weighted
	int nx = 0;
	int nc = 0;

	Iterator ip = phase2Model.listProposals();
	while (ip.hasNext()) {
	    
	    Proposal proposal = (Proposal)ip.next();
	    
	    // VETO: Proposal null.
	    if (proposal == null) continue;
	    
	    logger.log(1, "Check proposal "+proposal.getName());
	    
	    // VETO: Proposal outside activation period.        
	    if (proposal.getActivationDate() > end ||
		proposal.getExpiryDate() < start) {
		logger.log(2, "Proposal skipped: PROP_ACTIVATION");
		continue;
	    }
	    
	    // VETO: Proposal used up all allocated time.
	    Accounts acc = accModel.getAccounts(proposal.getFullPath());
	    if (acc == null) {
		logger.log(2, "Proposal skipped: PROP_NO_ACCOUNTS");
		    continue;
	    }
	    
	    Account total = acc.getAccount(AccountingModel.ACCOUNT_TOTAL);
	    if (total == null) {
		logger.log(2, "Proposal skipped: PROP_NO_TIME_ALLOC");
		continue;
	    }	
	    
	    if (total.getUsed() >= total.getAllocated()) {
		logger.log(2, "Proposal skipped: Acc.Total="+total);
		logger.log(2, "Proposal skipped: U="+total.getUsed()+" A="+total.getAllocated());
		logger.log(2, "Proposal skipped: PROP_TIME_ALLOC_EXCEEDED");
		continue;
	    }
	    
	    
	    Iterator iGroup = proposal.listAllGroups();		
	    while (iGroup.hasNext()) {
		
		Group group = (Group)iGroup.next();
		
		ExecutionStatistics hist = historyModel.getExecutionStatistics(group);
		long xt = execModel.getExecTime(group);
		
		// work out for each group (type-specific) what windows does it have
		// which start in (t1,t2) - they may end in (t1,t2) or may overrun.
		
		List windows = tcwc.listFeasibleWindows(group,
							hist,
							start, 
							end);
		
		if (windows == null)
		    continue;
		
		logger.log(1, "Found "+windows.size()+" windows in specified time span");
		
		Iterator it = windows.iterator();
		while (it.hasNext()) {
		    
		    TimeWindow w = (TimeWindow)it.next();
		    
		    logger.log(1, "Start Load Calc for Group: "+group.getName()+" in window: "+w);
		    
		    // needed for RN calculation..
		    TimeWindow srcwin2 = new TimeWindow(Math.max(start,w.start), Math.min(w.end, end+30*24*3600*1000L));
	    
		    // Now prune the start time to: time
		    w.start = Math.max(w.start, start);
		    // We are only interested in tonight not the whole window
		    w.end   = Math.min(w.end,   end);
		    TimeWindow srcwin = new TimeWindow(Math.max(start,w.start), end);
		    // arent start and w.start the same now ?
		    
		    EnvironmentSnapshot env = envModel.predictEnvironment(w.start);

		    // how much time is left in the window starting at w.start (or run start)
		    long rtw = tcwc.calculateRemainingTime(group, srcwin, w.start, env, hist);
		      
		   
		    int rn = tcwc.countRemainingNights(group, srcwin2, w.start, env, hist);
		    
		    logger.log(1, "XT="+ ((double)xt/1000.0)+"s, RTW = "+
			       ((double)rtw/1000.0)+"s, ("+((double)rtw/60000.0)+"m)"+ " RN="+rn);  
		  
		    // nominal 30 seconds - the group is executable in the window so add to total
		    if (rtw > 30*1000L) {
			// standard load.
			sumload += (double)xt;			
			nx++;
			// critical load
			if (rn == 1) {
			    critload += (double)xt;
			    nc++;
			} 

			// uw load
			uwload += (double)xt/(double)rn;

			// pw load
			pwload += puc.getUtility(group, w.start, env, hist);
			    
			logger.log(1, "Add exec for group in window: Total so far from "+nx+
				   " s.exec= "+sumload+", critical so far from "+nc+
				   " c.exec= "+critload+
				   " p.exec= "+pwload+
				   " u.exec= "+uwload);
			
		    }
		    
		}
		
	    } // next group
	    
	} // next prop
	
	// how  long is the night + astro_night
	long tt = start;
	double night = 0.0;
	double astro = 0.0;
	while (tt < end) {
	    Position sun = Astrometry.getSolarPosition(tt);
	    double elev = sun.getAltitude(tt, site);
	    if (elev < 0.0)
		night += 60*1000.0;
	    if (elev < Math.toRadians(-18.0))
		astro += 60*1000.0;
	    tt += 60*1000.0;	    
	}

	logger.log(1, "Load: "+sumload+
		   " NX: "+nx+" Night: "+night+" Astro: "+astro+
		   " StdLoad: "+(sumload/astro)+
		   " NC: "+nc+
		   " CritLoad:"+(critload/astro)+
		   " Pw Load: "+(pwload/astro)+
		   " Uw Load: "+(uwload/astro));
	
	// Fill out the statistics
	LoadStatistics lstats = new LoadStatistics();


	lstats.total = sumload;
	lstats.night = night;
	lstats.astro = astro;
	lstats.count = nx;
	lstats.crit  = critload;
	lstats.cc    = nc;
	lstats.pw    = pwload;
	lstats.uw    = uwload;
	return lstats;

    }


}
